傳奇基金經理人 彼得林區(Peter Lynch)曾說:「不進行研究的投資,就像打撲克從不看牌一樣,必然失敗。」不論是基本分析、技術分析或籌碼分析,都需要以數據為依歸,收集資料的目的,就是幫助我們作為投資決策的參考。
在資訊發達的時代,除了參考券商及看盤軟體整理的資訊,我們也可以自行透過網際網路取得公開資料,為了追求資料的正確性及完整性,我們取得資料的來源以交易所為主。今天,我們會介紹幾個最常用的網站,並說明在這些網站上可以取得什麼資料,來協助建構我們的股市資料庫。
臺灣證券交易所 簡稱 證交所,上市公司股票就是在證交所掛牌交易,也稱為 集中市場。臺灣的指標型公司與大型權值股都是在集中市場交易。證交所「發行量加權股價指數」就是新聞媒體報導的加權指數,它是用上市公司股票市值加權法計算的指數,是國內外用來衡量臺灣股市績效表現最主要的指標。在證交所網站上,每個交易日盤後會提供市場成交資訊與上市股票行情、三大法人買賣明細以及融資融券餘額統計等資訊。
證券櫃檯買賣中心 簡稱 櫃買中心,上櫃公司股票就是在櫃買中心掛牌交易,也稱為 櫃買市場、店頭市場。在櫃買中心掛牌的主要是股本較小的中小型企業。「櫃買指數」是採上櫃公司股票市值加權法計算的指數,主要反映上櫃股票市場整體的表現狀況。投資人可以在每個交易日盤後在櫃買中心網站取得市場成交資訊與上櫃股票行情、三大法人買賣明細以及融資融券餘額統計等資訊。
除了上櫃股票外,櫃買中心網站上也可以查詢興櫃股票行情。興櫃是公司上市、櫃前的預備市場,我國法令規定,公司申請上市或上櫃,需要在興櫃市場交易滿六個月。與上市櫃股票不同是,興櫃市場是採議價方式交易,因流通在外的股份較少,加上沒有單日漲跌幅 10% 的限制,因此投資風險較高,投資人如果要買賣興櫃股票必須先在開戶券商簽署「興櫃股票風險預告書」。
臺灣期貨交易所 簡稱 期交所,臺灣的期貨與選擇權商品都是在期交所掛牌交易。在期交所網站也會在每個交易日盤後公布各商品交易資訊、三大法人交易口數、契約金額以及大額交易人未沖銷部位結構等資訊。由於期貨有避險、投機與價格發現等功能,因此我們可以追蹤三大法人與大額交易人在臺股期貨與臺指選擇權的未平倉口數與契約金額,觀察機構法人與市場大戶對於臺股未來行情的看法。
臺灣集中保管結算所 簡稱 集保結算所,主要辦理有價證券集中保管帳簿劃撥制度、短期票券無實體化及集中結算交割制度等相關業務。集保結算所會在每週最後一個營業日結束後,公布最新的「集保戶股權分散表」,從集保戶股權分散表呈現的股權分布狀況,我們可以瞭解該股票的大戶與散戶持股比例。
為保障投資人權益,讓每個人都能取得正確、即時的公司資訊,我國法令規定上市、上櫃、興櫃以及公開發行公司的財務及營運資訊都須在 公開資訊觀測站 揭露。公開資訊觀測站彙集了公司每月營收、每季財報、股利政策,重大消息、股東會、公開法說會等重要資訊,我們可以經由查詢公開資訊觀測站揭露的資訊,瞭解上市櫃公司的營運概況。
以上我們說明了可以從哪些網站取得股市的相關資訊,在這些網站上,我們可以發現經常需要輸入股票代號才能查詢股票資訊,就像每位國民都有身份證字號一樣,每一檔股票也有專屬的股票代號。今天,我們就來瞭解如何取得所有上市及上櫃公司的股票代號。
為了查詢所有上市公司的股票代號,我們進入證交所網站,找到 證券編碼查詢 頁面。
證交所首頁 > 產品與服務 > 證券編碼 > 證券編碼查詢
然後點選「證券編碼 分類查詢」,我們可以看到以下網頁內容:
請在「市場別」選擇「1 .上市」;「有價證券別」選擇「1 .股票」,然後按下「確定」鈕:
我們可以得出以下 URL 即是上市公司股票清單:
https://isin.twse.com.tw/isin/class_main.jsp?market=1&issuetype=1
同查詢上市公司股票代號的查詢方式,進入「證券編碼 分類查詢」網頁,在「市場別」選擇「2.上櫃」;「有價證券別」選擇「4 .股票」,然後按下「確定」鈕:
我們可以得出以下 URL 即是上櫃公司股票清單:
https://isin.twse.com.tw/isin/class_main.jsp?market=2&issuetype=4
在剛剛查詢上市及上櫃公司股票清單的過程中,已經找到列出上市及上櫃公司股票清單的網址,我們的目標就是抓取這個網址的網頁內容,取得上市櫃公司股票的「有價證券代號」、「有價證券名稱」、「市場別」、「有價證券別」以及「產業別」等欄位資訊。
接下來,我們要實作網頁爬蟲取得上市櫃公司股票清單,請打開終端機使用 Nest CLI 建立一個命名為 scraper
的 Nest 應用程式:
$ nest new scraper
在建立應用程式的過程中,Nest CLI 會提示您想要使用哪個套件管理器(package manager),可用的選項有 npm
、yarn
、pnpm
。若無安裝 Yarn 或 pnpm,選擇 npm
即可。
關於 NPM、Yarn 及 pnpm 之間的差異,可以參考《Understanding differences between npm, yarn and pnpm》一文的說明。
Nest CLI 預設建立的應用程式專案結構如下:
.
├── src
| ├─ app.controller.ts
| ├─ app.controller.spec.ts
| ├─ app.module.ts
| ├─ app.service.ts
| └─ main.ts
├── test
| ├── app.e2e-spec.ts
| └── jest-e2e.json
├── .eslintrc.js
├── .gitignore
├── .prettierrc
├── nest-cli.json
├── package.json
├── package-lock.json
├── tsconfig.json
├── tsconfig.build.json
└── README.md
在 src
目錄下放置的是應用程式的原始碼,我們不會使用到 app.controller.ts
、app.controller.spec.ts
、app.service.ts
檔案,可先移除,並打開 app.module.ts
檔案,將 AppModule
調整如下:
import { Module } from '@nestjs/common';
@Module({
imports: [],
})
export class AppModule {}
接著,我們透過 Nest CLI 建立 ScraperModule
:
$ nest g module scraper
Nest CLI 會在專案的 src
目錄下新增 scraper
資料夾,並在該處建立 scraper.module.ts
檔案。
Nest 應用程式是由 Modules 所組成,至少包含一個 root module,即
AppModule
,以及數個 modules。
我們要在 ScraperModule
下新增 TwseScraperService
,使用以下 Nest CLI 指令:
$ nest g service scraper/twse-scraper --flat --no-spec
我們希望產生的
twse-scraper.service.ts
檔案是與scraper.module.ts
置於同一個目錄,因此使用--flat
選項。為了簡化程式範例,我們使用--no-spec
選項忽略建立單元測試檔。
執行命令後,Nest CLI 會在 src/scraper
目錄下建立 twse-scraper.service.ts
檔案,並且將 TwseScraperService
加入至 ScraperModule
的 providers
設定。
在 Nest 中,Providers 是可以在 Module 被依賴注入的類別,我們可以在一個 Provider 撰寫商業邏輯,提供像是 service、repository、factory、helper 等概念。
下一步,我們還需要安裝幾個套件來幫助我們取得網頁內容:
$ npm install --save @nestjs/axios cheerio iconv-lite
以下說明這些套件的用途:
@nestjs/axio
:NestJS 官方基於 axios 實作的 HTTP Module,用來請求網頁資料。cheerio
:用來從 HTML 中擷取我們要的資料,API 方法類似 JQuery。iconv-lite
:取得的網頁資料因編碼問題,中文的部分可能是亂碼,因此我們使用該套件將它轉換為正確的編碼。上述套件安裝完畢後,我們開啟 src/scraper/scraper.module.ts
檔案,設定 ScraperModule
匯入 @nestjs/axios
模組:
import { Module } from '@nestjs/common';
import { HttpModule } from '@nestjs/axios';
import { TwseScraperService } from './twse-scraper.service';
@Module({
imports: [HttpModule],
providers: [TwseScraperService],
})
export class ScraperModule {}
然後開啟 src/scraper/twse-scraper.service.ts
檔案,在 TwseScraperService
實作 fetchListedStocks()
方法,取得所有上市或上櫃公司股票清單:
import * as cheerio from 'cheerio';
import * as iconv from 'iconv-lite';
import { Injectable } from '@nestjs/common';
import { HttpService } from '@nestjs/axios';
import { firstValueFrom } from 'rxjs';
@Injectable()
export class TwseScraperService {
constructor(private httpService: HttpService) {}
async fetchListedStocks(options?: { market: 'TSE' | 'OTC' }) {
const url = options?.market === 'OTC'
? 'https://isin.twse.com.tw/isin/class_main.jsp?market=2&issuetype=4'
: 'https://isin.twse.com.tw/isin/class_main.jsp?market=1&issuetype=1';
// 取得 HTML 並轉換為 Big-5 編碼
const page = await firstValueFrom(this.httpService.get(url, { responseType: 'arraybuffer' }))
.then(response => iconv.decode(response.data, 'big5'));
// 使用 cheerio 載入 HTML 以取得表格的 table rows
const $ = cheerio.load(page);
const rows = $('.h4 tr');
// 遍歷每個 table row 並將其轉換成我們想要的資料格式
const data = rows.slice(1).map((i, el) => {
const td = $(el).find('td');
return {
symbol: td.eq(2).text().trim(), // 股票代碼
name: td.eq(3).text().trim(), // 股票名稱
market: td.eq(4).text().trim(), // 市場別
industry: td.eq(6).text().trim(), // 產業別
};
}).toArray();
return data;
}
}
在 fetchListedStocks()
方法中,提供 options
可選參數,TSE
代表取得上市公司股票;OTC
代表取得上櫃公司股票。若未指定,則預設取得上市公司股票清單。
我們在 TwseScraperService
依賴注入 HttpService
用來取得網頁內容。由於 @nestjs/axios
模組提供的 HttpService
回傳的是一個 Observable 物件,因此我們用 rxjs
提供的 firstValueFrom()
函式取得回應資料。
因為網頁編碼的問題,取得的回應資料的中文部分可能是亂碼,因此我們透過 iconv.decode()
方法將其轉換為轉換為正確的 Big-5 編碼。
完成編碼轉換後,因為請求的回應資料是一個 HTML 頁面,我們使用 cheerio.load()
載入資料,並找到正確的 HTML 元素,逐列地擷取我們所需要的欄位資訊。
完成這支爬蟲程式後,我們只要呼叫 TwseScraperService
的 fetchTseListed()
方法,就可以取得上市公司股票清單。
為了方便測試 TwseScraperService
的 fetchListedStocks()
方法,我們可以暫時加入 onApplicationBootstrap()
這個 lifecyle hook:
@Injectable()
export class TwseScraperService implements OnApplicationBootstrap {
constructor(private httpService: HttpService) {}
async onApplicationBootstrap() {
const tse = await this.fetchListedStocks({ market: 'TSE' });
console.log(tse); // 顯示上市公司股票清單
const otc = await this.fetchListedStocks({ market: 'OTC' });
console.log(otc); // 顯示上櫃公司股票清單
}
...
}
Nest 提供 Lifecycle Events 機制,
onApplicationBootstrap()
會在所有 modules 都被初始化後執行。注意這裡加入onApplicationBootstrap()
方法只是方便查看執行結果,確認程式執行無誤後記得移除該方法,否則每次執行應用程式前都會先執行onApplicationBootstrap()
內的程式碼。
然後打開終端機輸入以下指令執行應用程式:
$ npm run start:dev
如果成功取得上市公司股票清單,終端機應該會顯示如下內容:
[
{ symbol: '1101', name: '台泥', market: '上市', industry: '水泥工業' },
{ symbol: '1102', name: '亞泥', market: '上市', industry: '水泥工業' },
{ symbol: '1103', name: '嘉泥', market: '上市', industry: '水泥工業' },
{ symbol: '1104', name: '環泥', market: '上市', industry: '水泥工業' },
{ symbol: '1108', name: '幸福', market: '上市', industry: '水泥工業' },
{ symbol: '1109', name: '信大', market: '上市', industry: '水泥工業' },
{ symbol: '1110', name: '東泥', market: '上市', industry: '水泥工業' },
{ symbol: '1201', name: '味全', market: '上市', industry: '食品工業' },
{ symbol: '1203', name: '味王', market: '上市', industry: '食品工業' },
{ symbol: '1210', name: '大成', market: '上市', industry: '食品工業' },
... more items
]
如果成功取得上櫃公司股票清單,終端機應該會顯示如下內容:
[
{ symbol: '1240', name: '茂生農經', market: '上櫃', industry: '農業科技業' },
{ symbol: '1258', name: '其祥-KY', market: '上櫃', industry: '食品工業' },
{ symbol: '1259', name: '安心', market: '上櫃', industry: '觀光事業' },
{ symbol: '1264', name: '德麥', market: '上櫃', industry: '食品工業' },
{ symbol: '1268', name: '漢來美食', market: '上櫃', industry: '觀光事業' },
{ symbol: '1336', name: '台翰', market: '上櫃', industry: '電子零組件業' },
{ symbol: '1565', name: '精華', market: '上櫃', industry: '生技醫療業' },
{ symbol: '1569', name: '濱川', market: '上櫃', industry: '電腦及週邊設備業' },
{ symbol: '1570', name: '力肯', market: '上櫃', industry: '電機機械' },
{ symbol: '1580', name: '新麥', market: '上櫃', industry: '電機機械' },
... more items
]
本系列文已正式出版為《Node.js 量化投資全攻略:從資料收集到自動化交易系統建構實戰》。本書新增了全新內容和實用範例,為你提供更深入的學習體驗!歡迎參考選購,開始你的量化投資之旅!
天瓏網路書店連結:https://www.tenlong.com.tw/products/9786263336070